package com.linsen.core.poitl.core;

import com.alibaba.fastjson.JSON;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.render.compute.EnvModel;
import com.deepoove.poi.render.compute.RenderDataCompute;
import com.deepoove.poi.render.processor.DocumentProcessor;
import com.deepoove.poi.render.processor.EnvIterator;
import com.deepoove.poi.resolver.TemplateResolver;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.ReflectionUtils;
import com.deepoove.poi.util.TableTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.*;

import java.util.*;

/**
 * word模板替换，表格双重循环（外层循环+子循环）
 * https://blog.csdn.net/qq_24585103/article/details/131233839
 */
@Slf4j
public class MultilevelLoopRowTableRenderPolicy implements RenderPolicy {
    private final String prefix;
    private final String suffix;
    private final boolean onSameLine;
    private final String titleName;
    private final String contextName;

    public MultilevelLoopRowTableRenderPolicy(String titleName, String contextName) {
        this(titleName, contextName, false);
    }

    public MultilevelLoopRowTableRenderPolicy(String titleName, String contextName, boolean onSameLine) {
        this("{[", "]}", titleName, contextName, onSameLine);
    }

    public MultilevelLoopRowTableRenderPolicy(String prefix, String suffix, String titleName, String contextName) {
        this(prefix, suffix, titleName, contextName, false);
    }

    public MultilevelLoopRowTableRenderPolicy(String prefix, String suffix, String titleName, String contextName, boolean onSameLine) {
        this.prefix = prefix;
        this.suffix = suffix;
        this.onSameLine = onSameLine;
        this.titleName = titleName;
        this.contextName = contextName;
    }

    @Override
    public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
        try {
            RunTemplate runTemplate = (RunTemplate) eleTemplate;
            XWPFRun run = runTemplate.getRun();

            if (!TableTools.isInsideTable(run)) {
                throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
            }
            XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();

            final XWPFTable table = tagCell.getTableRow().getTable();

            if (data instanceof Iterable) {
                run.setText("", 0);
                TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));
                //读取模板所在起始行
                int templateRowIndex = getTemplateRowIndex(tagCell);
                //获取多行模板
                List<XWPFTableRow> templateRows = getAllTemplateRow(table, templateRowIndex);
                //保存子标题内容，以便后续操作中获取光标
                final XWPFTableRow firstTitleTemplateRow = templateRows.get(0);
                //保存子列表内容，以便后续中获取光标操作
                final XWPFTableRow firstSonRowTemplateRow = templateRows.get(1);
                int index = 0;
                //title row num
                int titleRowPosition = getRowIndex(firstTitleTemplateRow);
                //content row num
                int sonRowPosition = getRowIndex(firstSonRowTemplateRow);
                //foreach data
                Iterator<?> iterator = ((Iterable<?>) data).iterator();
                boolean hasNext = iterator.hasNext();
                while (hasNext) {
                    Object root = iterator.next();
                    hasNext = iterator.hasNext();
                    //parse data
                    Map<String, Object> jsonObject = JSON.parseObject(JSON.toJSONString(root));
                    String dqmc = (String) jsonObject.get(titleName);
                    //copy title row start
                    if (!copy(table, firstTitleTemplateRow, titleRowPosition)) {
                        throw new RenderException("create new table fail...");
                    }
                    XWPFTableRow dqnewRow = table.getRow(titleRowPosition);
                    setTableRow(table, dqnewRow, titleRowPosition);
                    //copy title row end

                    //render title
                    Map<String, Object> map = new HashMap<>();
                    map.put(titleName, dqmc);
                    List<XWPFTableCell> dqcells = dqnewRow.getTableCells();
                    RenderDataCompute dqDataCompute = template.getConfig()
                            .getRenderDataComputeFactory()
                            .newCompute(EnvModel.of(map, EnvIterator.makeEnv(++index, hasNext)));
                    dqcells.forEach(tableCell -> {
                        List<MetaTemplate> metaTemplates = resolver
                                .resolveBodyElements(tableCell.getBodyElements());
                        new DocumentProcessor(template, resolver, dqDataCompute)
                                .process(metaTemplates);
                    });
                    //table add to row . so ++
                    titleRowPosition++;
                    //get son list
                    List sonList = (List) jsonObject.get(contextName);
                    //plus son list size
                    titleRowPosition = titleRowPosition + ((Collection<?>) sonList).size();

                    //foreach son list
                    Iterator<?> sonDataIterator = ((Iterable<?>) sonList).iterator();
                    boolean sonDataIteratorHasNext = sonDataIterator.hasNext();
                    while (sonDataIteratorHasNext) {
                        Object sonData = sonDataIterator.next();
                        sonDataIteratorHasNext = sonDataIterator.hasNext();

                        //copy son row start
                        if (!copy(table, firstSonRowTemplateRow, sonRowPosition)) {
                            throw new RenderException("create new table fail...");
                        }
                        XWPFTableRow newRow = table.getRow(sonRowPosition);
                        setTableRow(table, newRow, sonRowPosition);
                        //copy son row end
                        //render title
                        List<XWPFTableCell> cells = newRow.getTableCells();
                        RenderDataCompute dataCompute = template.getConfig()
                                .getRenderDataComputeFactory()
                                .newCompute(EnvModel.of(sonData, EnvIterator.makeEnv(++index, sonDataIteratorHasNext)));
                        cells.forEach(tableCell -> {
                            List<MetaTemplate> metaTemplates = resolver
                                    .resolveBodyElements(tableCell.getBodyElements());
                            new DocumentProcessor(template, resolver, dataCompute)
                                    .process(metaTemplates);
                        });
                        //add son row. so ++
                        sonRowPosition++;
                    }
                    //add title row. so ++
                    sonRowPosition = sonRowPosition + 1;
                }

                //remove last template row
                removeTableRow(table, table.getNumberOfRows() - getMultipleTemplateRowNum(), templateRows.size());
            }
        } catch (Exception e) {
            throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
        }
    }

    private List<XWPFTableRow> getAllTemplateRow(XWPFTable table, int startIndex) {
        int tempRowNum = getMultipleTemplateRowNum();
        List<XWPFTableRow> rows = table.getRows();
        return new Vector<>(rows.subList(startIndex, startIndex + tempRowNum));
    }

    private int getMultipleTemplateRowNum() {
        return 2;
    }

    private int getTemplateRowIndex(XWPFTableCell tagCell) {
        XWPFTableRow tagRow = tagCell.getTableRow();
        return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
    }


    private void removeTableRow(XWPFTable table, int startIndex, int size) {
        for (int i = 0; i < size; ++i) {
            table.removeRow(startIndex);
        }
    }


    @SuppressWarnings("unchecked")
    private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
        List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
        rows.set(pos, templateRow);
        table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
    }

    private int getRowIndex(XWPFTableRow row) {
        List<XWPFTableRow> rows = row.getTable().getRows();
        return rows.indexOf(row);
    }

    /**
     * description 深拷贝一行数据
     *
     * @param table
     * @param sourceRow
     * @param rowIndex
     * @return
     * @date 2023/6/15 16:23
     * @author Eugene
     */
    public boolean copy(XWPFTable table, XWPFTableRow sourceRow, int rowIndex) {
        //在表格指定位置新增一行
        XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
        //复制行属性
        targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
        List<XWPFTableCell> cellList = sourceRow.getTableCells();
        if (null == cellList) {
            return false;
        }
        //复制列及其属性和内容
        XWPFTableCell targetCell = null;
        for (XWPFTableCell sourceCell : cellList) {
            targetCell = targetRow.addNewTableCell();
            //列属性
            targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
            //段落属性
            if (sourceCell.getParagraphs() != null && sourceCell.getParagraphs().size() > 0) {
                targetCell.getParagraphs().get(0).getCTP().setPPr(sourceCell.getParagraphs().get(0).getCTP().getPPr());
                if (sourceCell.getParagraphs().get(0).getRuns() != null && sourceCell.getParagraphs().get(0).getRuns().size() > 0) {
                    XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
                    cellR.setText(sourceCell.getText());
                    cellR.setBold(sourceCell.getParagraphs().get(0).getRuns().get(0).isBold());
                } else {
                    targetCell.setText(sourceCell.getText());
                }
            } else {
                targetCell.setText(sourceCell.getText());
            }
        }
        return true;
    }
}
